﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Microsoft.Surface.Presentation.Input;
using ZOIL.Framework.Core;
using ZOIL.Framework.Presentation;

namespace ZOIL.Library.Behaviors
{
    public static class ZObjectDragDropBehavior
    {
        #region events

        public delegate void DragDropEventHandler(object sender, ZObjectDragDropEventArgs args);

        public static readonly RoutedEvent DragStartedEvent = EventManager.RegisterRoutedEvent("DragStarted", RoutingStrategy.Bubble,
                                                                                               typeof (DragDropEventHandler),
                                                                                               typeof (ZObjectDragDropBehavior));

        public static readonly RoutedEvent DragFinishedEvent = EventManager.RegisterRoutedEvent("DragFinished", RoutingStrategy.Bubble,
                                                                                                typeof (DragDropEventHandler),
                                                                                                typeof (ZObjectDragDropBehavior));

        public static readonly RoutedEvent DragDeltaEvent = EventManager.RegisterRoutedEvent("DragDelta", RoutingStrategy.Bubble,
                                                                                             typeof (DragDropEventHandler),
                                                                                             typeof (ZObjectDragDropBehavior));

        #endregion

        #region attached properties

        #region IsDragging attached property (for reading out current dragging status)

        public static DependencyProperty IsDraggingProperty = DependencyProperty.RegisterAttached("IsDragging",
                                                                                                  typeof (bool),
                                                                                                  typeof (ZObjectDragDropBehavior),
                                                                                                  new FrameworkPropertyMetadata(false,
                                                                                                                                IsDraggingChanged));

        private static void IsDraggingChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
        {
        }

        public static void SetIsDragging(DependencyObject target, bool value)
        {
            target.SetValue(IsDraggingProperty, value);
        }

        public static bool GetIsDragging(DependencyObject d)
        {
            return (bool) d.GetValue(IsDraggingProperty);
        }

        #endregion

        #region IsDraggable attached property (for setting drag & drop behavior active or unactive)

        public static DependencyProperty IsDraggableProperty = DependencyProperty.RegisterAttached("IsDraggable",
                                                                                                   typeof (bool),
                                                                                                   typeof (ZObjectDragDropBehavior),
                                                                                                   new FrameworkPropertyMetadata(false,
                                                                                                                                 IsDraggableChanged));

        private static readonly Dictionary<UIElement, ZObjectDragInformation> ElementsBeingDragged =
            new Dictionary<UIElement, ZObjectDragInformation>();

        #region mouse

        private static void OnMouseDown(object sender, MouseButtonEventArgs e)
        {
            var element = sender as UIElement;

            if (e.StylusDevice != null)
            {
                return;
            }

            if (ElementsBeingDragged.ContainsKey(element))
            {
                return;
            }

            var info = new ZObjectDragInformation(e.GetPosition(null));

            info.ElementBeingDragged = ZBehavior.FindCanvasChild(element);

            //set ZIndex of ElementBeingDragged
            if (info.ElementBeingDragged is ZComponent)
            {
                Panel.SetZIndex(info.ElementBeingDragged, ZComponent.ZIndexCounter++);
            }

            info.OriginalFrameworkElement = (e.Source as FrameworkElement != null)
                                                ? e.Source as FrameworkElement
                                                : info.ElementBeingDragged as FrameworkElement;
            info.Bottom = Canvas.GetBottom(info.ElementBeingDragged);
            info.Top = Canvas.GetTop(info.ElementBeingDragged);
            info.Left = Canvas.GetLeft(info.ElementBeingDragged);
            info.Right = Canvas.GetRight(info.ElementBeingDragged);
            info.OriginalPositionOnUIElement = e.GetPosition(element);
            // TODO: replace using TransformToAncestor()
            info.ParentLandscapes = ZTreeHelper.TryFindParents<ZInformationLandscape>(element);

            ElementsBeingDragged[element] = info;

            element.CaptureMouse();
            e.Handled = true;
        }

        private static void OnMouseUp(object sender, MouseButtonEventArgs e)
        {
            var element = sender as UIElement;

            if (ElementsBeingDragged.ContainsKey(sender as UIElement))
            {
                ZObjectDragInformation info = ElementsBeingDragged[sender as UIElement];
                if (!GetIsDragging(element))
                {
                    //apply click zoom here, because we do not drag
                    FrameworkElement zoomTarget = ZTreeHelper.GetClosestZoomTarget(info.ElementBeingDragged as FrameworkElement,
                                                                                   info.OriginalFrameworkElement);
                    if (zoomTarget != null)
                    {
                        info.ParentLandscapes.First().ZoomOnto(zoomTarget, false, true);
                        zoomTarget.RaiseEvent(new RoutedEventArgs(ZInformationLandscape.ClickZoomEvent, zoomTarget));
                    }
                }

                ElementsBeingDragged.Remove(sender as UIElement);
            }
            element.ReleaseMouseCapture();
            e.Handled = true;

            //Raise DragFinishedEvent and set IsDragging to false
            var args = new ZObjectDragDropEventArgs();
            args.RoutedEvent = DragFinishedEvent;
            args.Source = element;
            args.CurrentDragPoint = e.GetPosition(null);
            element.RaiseEvent(args);

            //hack, to raise the event on the clipboard (JS)
            //removed hack ! since event has been raised two times which causes unforseen behavior (MZ)
            //FrameworkElement ui = Mouse.DirectlyOver as FrameworkElement;
            //if(ui != null) ui.RaiseEvent(args);

            SetIsDragging(element, false);
        }

        private static void OnMouseMove(object sender, MouseEventArgs e)
        {
            var element = sender as UIElement;
            if (ElementsBeingDragged.ContainsKey(element))
            {
                ZObjectDragInformation info = ElementsBeingDragged[element];
                Point currentPoint = e.GetPosition(null);

                double scale = info.GetScaleFactor();

                Vector diff = Point.Subtract(currentPoint, info.OriginalPositionOnWindow);

                //double xdiff = (currentPoint.X - info.OriginalMousePositionOnWindow.X);
                //double ydiff = (currentPoint.Y - info.OriginalMousePositionOnWindow.Y);

                if (diff.Length > 5)
                {
                    if (!GetIsDragging(element))
                    {
                        SetIsDragging(element, true);

                        //Raise OnDragStartedEvent and set IsDragging to true
                        var args = new ZObjectDragDropEventArgs();
                        args.RoutedEvent = DragStartedEvent;
                        args.Source = element;
                        args.CurrentDragPoint = e.GetPosition(null);
                        element.RaiseEvent(args);
                        //element.SetValue(ZObjectDragDropBehavior.IsDraggingProperty, true);
                    }
                    else
                    {
                        //Raise DragDeltaEvent
                        var args = new ZObjectDragDropEventArgs();
                        args.CurrentDragPoint = currentPoint;
                        args.RoutedEvent = DragDeltaEvent;
                        args.Source = element;
                        element.RaiseEvent(args);
                    }
                }

                double newX = info.Left + diff.X/scale;
                double newY = info.Top + diff.Y/scale;

                Canvas.SetLeft(info.ElementBeingDragged, newX);
                Canvas.SetTop(info.ElementBeingDragged, newY);

                e.Handled = true;
            }
        }

        #endregion

        private static void IsDraggableChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
        {
            //if((bool)e.NewValue)
            //{
            //    if(target is FrameworkElement)
            //    {
            //        (target as FrameworkElement).Cursor = Cursors.Hand;
            //    }
            //}

            //following section forwards a change of IsDraggableChanged property to method ZBehavior.HandleChange, 
            //which then will (un)register event handlers according to new value of property.

            // TODO: do this for all possible devices (e.g. Mouse, Surface, Wii) 
            // TODO: there should be a more elegant solution than to just go through all devices without much thought. Ideally devices become dynamically (un)loadable
            ZBehavior.HandleChange(e.OldValue, e.NewValue, target as ZComponent, Mouse.MouseDownEvent, new MouseButtonEventHandler(OnMouseDown));
            ZBehavior.HandleChange(e.OldValue, e.NewValue, target as ZComponent, Mouse.MouseUpEvent, new MouseButtonEventHandler(OnMouseUp));
            ZBehavior.HandleChange(e.OldValue, e.NewValue, target as ZComponent, Mouse.MouseMoveEvent, new MouseEventHandler(OnMouseMove));

            //Wii

            //Surface
            //ZBehavior.HandleChange(e.OldValue, e.NewValue, target as UIElement, UIElement.TouchDownEvent, new EventHandler<TouchEventArgs>(ZObjectSurfaceHandler.OnContactDown));
            //ZBehavior.HandleChange(e.OldValue, e.NewValue, target as UIElement, UIElement.TouchUpEvent, new EventHandler<TouchEventArgs>(ZObjectSurfaceHandler.OnContactUp));
            //ZBehavior.HandleChange(e.OldValue, e.NewValue, target as UIElement, TouchExtensions.TapGestureEvent, new EventHandler<TouchEventArgs>(ZObjectSurfaceHandler.OnContactTapGesture));
            //ZBehavior.HandleChange(e.OldValue, e.NewValue, target as UIElement, TouchExtensions.HoldGestureEvent, new EventHandler<TouchEventArgs>(ZObjectSurfaceHandler.OnContactHold));

            //TODO: besseren Weg finden um 2x/3x Registrierung der Events zu verhindern
            if (!ZObjectResizeBehavior.GetIsResizable(target as UIElement) && !ZObjectRotateBehavior.GetIsRotatable(target as ZComponent))
            {
                ZBehavior.HandleChange(e.OldValue, e.NewValue, target as UIElement, TouchExtensions.TapGestureEvent,
                                       new EventHandler<TouchEventArgs>(ZObjectSurfaceHandler.OnContactTapGesture));
                ZBehavior.HandleChange(e.OldValue, e.NewValue, target as UIElement, UIElement.ManipulationStartingEvent,
                                       new EventHandler<ManipulationStartingEventArgs>(ZObjectSurface2Handler.ManipulationStarting));
                ZBehavior.HandleChange(e.OldValue, e.NewValue, target as UIElement, UIElement.ManipulationDeltaEvent,
                                       new EventHandler<ManipulationDeltaEventArgs>(ZObjectSurface2Handler.ManipulationDelta));
                ZBehavior.HandleChange(e.OldValue, e.NewValue, target as UIElement, UIElement.ManipulationInertiaStartingEvent,
                                       new EventHandler<ManipulationInertiaStartingEventArgs>(ZObjectSurface2Handler.ManipulationInertiaStarting));
                ZBehavior.HandleChange(e.OldValue, e.NewValue, target as UIElement, UIElement.ManipulationCompletedEvent,
                                       new EventHandler<ManipulationCompletedEventArgs>(ZObjectSurface2Handler.ManipulationCompleted));
                ZBehavior.HandleChange(e.OldValue, e.NewValue, target as UIElement, UIElement.TouchDownEvent,
                                       new EventHandler<TouchEventArgs>(ZObjectSurface2Handler.OnTouchDown));
            }

            //Windows 7
            //ZBehavior.HandleChange(e.OldValue, e.NewValue, target as UIElement, UIElement.StylusDownEvent, new StylusDownEventHandler(ZObjectWindows7Handler.OnStylusDown));
            //ZBehavior.HandleChange(e.OldValue, e.NewValue, target as UIElement, UIElement.StylusMoveEvent, new StylusEventHandler(ZObjectWindows7Handler.OnStylusMove));
            //ZBehavior.HandleChange(e.OldValue, e.NewValue, target as UIElement, UIElement.StylusUpEvent, new StylusEventHandler(ZObjectWindows7Handler.OnStylusUp));
            //ZBehavior.HandleChange(e.OldValue, e.NewValue, target as UIElement, UIElement.StylusSystemGestureEvent, new StylusSystemGestureEventHandler(ZObjectWindows7Handler.OnStylusSystemGesture));
        }

        //---------------------------------------------------------
        //device specific implementation of dragging behavior:
        //these manage the currently dragged objects and raise the events to issue commands on UIElement

        public static void SetIsDraggable(ZComponent target, bool value)
        {
            target.SetValue(IsDraggableProperty, value);
        }

        public static bool GetIsDraggable(ZComponent d)
        {
            return (bool) d.GetValue(IsDraggableProperty);
        }

        #endregion

        #region OnDragStarted attached property (for setting the RoutedCommand which is executed on UIElement after drag is started)

        public static DependencyProperty OnDragStartedProperty = DependencyProperty.RegisterAttached("OnDragStarted",
                                                                                                     typeof (RoutedCommand),
                                                                                                     typeof (ZObjectDragDropBehavior),
                                                                                                     new FrameworkPropertyMetadata(null,
                                                                                                                                   OnDragStartedChanged));

        private static void OnDragStartedChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
        {
            ZBehavior.HandleChange(e.OldValue, e.NewValue, target as UIElement, DragStartedEvent, new DragDropEventHandler(OnDragStarted));
        }

        private static void OnDragStarted(object sender, ZObjectDragDropEventArgs args)
        {
            ZBehavior.ExecuteCommand(sender as UIElement, OnDragStartedProperty, args);
        }

        public static void SetOnDragStarted(DependencyObject target, RoutedCommand value)
        {
            target.SetValue(OnDragStartedProperty, value);
        }

        public static RoutedCommand GetOnDragStarted(DependencyObject d)
        {
            return d.GetValue(OnDragStartedProperty) as RoutedCommand;
        }

        #endregion

        #region OnDragDelta attached property (for setting the RoutedCommand which is executed on UIElement while dragging position changes)

        public static DependencyProperty OnDragDeltaProperty = DependencyProperty.RegisterAttached("OnDragDelta",
                                                                                                   typeof (RoutedCommand),
                                                                                                   typeof (ZObjectDragDropBehavior),
                                                                                                   new FrameworkPropertyMetadata(null,
                                                                                                                                 OnDragDeltaChanged));

        private static void OnDragDeltaChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
        {
            ZBehavior.HandleChange(e.OldValue, e.NewValue, target as UIElement, DragDeltaEvent, new DragDropEventHandler(OnDragDelta));
        }

        private static void OnDragDelta(object sender, ZObjectDragDropEventArgs args)
        {
            ZBehavior.ExecuteCommand(sender as UIElement, OnDragDeltaProperty, args);
        }

        public static void SetOnDragDelta(DependencyObject target, RoutedCommand value)
        {
            target.SetValue(OnDragDeltaProperty, value);
        }

        public static RoutedCommand GetOnDragDelta(DependencyObject d)
        {
            return d.GetValue(OnDragDeltaProperty) as RoutedCommand;
        }

        #endregion

        #region OnDragFinished attached property (for setting the RoutedCommand which is executed on UIElement while dragging is finished)

        public static DependencyProperty OnDragFinishedProperty = DependencyProperty.RegisterAttached("OnDragFinished",
                                                                                                      typeof (RoutedCommand),
                                                                                                      typeof (ZObjectDragDropBehavior),
                                                                                                      new FrameworkPropertyMetadata(null,
                                                                                                                                    OnDragStartedFinished));

        private static void OnDragStartedFinished(DependencyObject target, DependencyPropertyChangedEventArgs e)
        {
            ZBehavior.HandleChange(e.OldValue, e.NewValue, target as UIElement, DragFinishedEvent, new DragDropEventHandler(OnDragFinished));
        }

        private static void OnDragFinished(object sender, ZObjectDragDropEventArgs args)
        {
            ZBehavior.ExecuteCommand(sender as UIElement, OnDragFinishedProperty, args);
        }

        public static void SetOnDragFinished(DependencyObject target, RoutedCommand value)
        {
            target.SetValue(OnDragFinishedProperty, value);
        }

        public static RoutedCommand GetOnDragFinished(DependencyObject d)
        {
            return d.GetValue(OnDragFinishedProperty) as RoutedCommand;
        }

        #endregion

        #endregion
    }

    public class ZObjectDragDropEventArgs : RoutedEventArgs
    {
        public Point CurrentDragPoint { get; set; }

        public Vector DragVector { get; set; }
    }
}